﻿using System;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using System.ServiceModel.Channels;
using System.Text;
using System.Web;
using System.Xml;

namespace MockingBird.Simulator.Framework
{
    using System.Diagnostics;
    using System.Net;
    using System.Threading;
    using Interfaces;
    using MockingBird.Simulator.Framework.Configuration;

    /// <summary>
    /// This class encapsulates the execution context of mockingbird so that it can be isolated
    /// from runtime environments such as HTTP or other WCF OperationContext. This has been flattened
    /// and contains only the essential elements for the core engine to function so that it does not
    /// have any dependency on the runtime environment. 
    /// </summary>
    public class ExecutionContext : IExecutionContext
    {
        public ApplicationConfiguration AppConfig { get; set; }
        public string PhysicalApplicationPath { get; set; }

        public Uri Uri { get; set; }

        public NameValueCollection HttpHeaders { get; set; }
        public string ContentType { get; set; }
        public string RequestMessage { get; set; }

        public XmlDocument ResponseMessage { get; set; }

        public string EndpointFullName { get; set; }
        public string EndpointName { get; private set; }
        public string RequestRootElementName { get; private set; }

        public string Action { get; set; }

        public bool IsOneWay { get; set; }

        #region "constructor(s)"
        /// <summary>
        /// Default constructor
        /// </summary>
        private ExecutionContext(ApplicationConfiguration appConfig)
        {
            Debug.WriteLine(string.Format("ExecutionContext.ctor on thread ID: {0}", Thread.CurrentThread.ManagedThreadId));
            this.AppConfig = appConfig;
        }

        public ExecutionContext(ApplicationConfiguration appConfig, HttpContext httpContext)
            : this(appConfig)
        {
            InitializeWithHttpContext(httpContext);
        }

        public ExecutionContext(ApplicationConfiguration appConfig, Message message)
            : this(appConfig)
        {
            InitializeWithWcfMessage(message);
        }

        /// <summary>
        /// Constructor which allows this to be initialized from the HttpContext
        /// </summary>
        /// <param name="context">Current HttpContext</param>
        private void InitializeWithHttpContext(HttpContext context)
        {
            // assign the physical application path
            PhysicalApplicationPath = context.Request.PhysicalApplicationPath;

            // assign the Uri
            Uri = context.Request.Url;

            // derive the endpoints
            this.EndpointFullName = Uri.Segments[Uri.Segments.Length - 1];
            this.EndpointName = Path.GetFileNameWithoutExtension(EndpointFullName);

            // assign the headers
            this.HttpHeaders = new NameValueCollection();
            foreach (string key in context.Request.Headers.Keys)
            {
                this.HttpHeaders.Add(key, context.Request.Headers.Get(key));
            }
            this.Action = context.Request.Headers["SOAPAction"];

            // assign the content type
            this.ContentType = context.Request.ContentType;

            // assign the request message
            this.GetRequestMessage(context);

            // assign the root element
            this.GetRequestRootElement();
        }

        private void InitializeWithWcfMessage(Message msg)
        {
            PhysicalApplicationPath = Assembly.GetCallingAssembly().CodeBase; // Longshot, It's not used anywhere
            Uri = msg.Headers.To;//oc.Channel.LocalAddress.Uri;

            //this.EndpointName = Path.GetFileNameWithoutExtension(this.Uri.AbsolutePath);
            //this.EndpointFullName = Path.Combine(AppConfig.EndpointsRoot, this.EndpointName);
            this.EndpointFullName = this.Uri.Segments[this.Uri.Segments.Length - 1];
            this.EndpointName = Path.GetFileNameWithoutExtension(this.EndpointFullName);

            try
            {
                HttpRequestMessageProperty prop = (HttpRequestMessageProperty)msg.Properties[HttpRequestMessageProperty.Name];
                if (null != prop)
                {
                    this.ContentType = prop.Headers[HttpRequestHeader.ContentType];
                }
            }
            catch { }
            this.Action = msg.Headers.Action;
            this.GetRequestMessageAndRootElement(msg);
        }

        private void GetRequestMessageAndRootElement(Message msg)
        {
            XmlDocument xdoc = new XmlDocument();
            xdoc.Load(msg.GetReaderAtBodyContents());
            this.RequestMessage = xdoc.OuterXml;
            this.RequestRootElementName = xdoc.DocumentElement.Name;
        }

        private void GetRequestMessage(HttpContext context)
        {
            StreamReader requestReader = new StreamReader(context.Request.InputStream, Encoding.UTF8);
            byte[] requestBytes;
            requestBytes = Encoding.UTF8.GetBytes(requestReader.ReadToEnd());
            this.RequestMessage = System.Text.Encoding.UTF8.GetString(requestBytes, 0, requestBytes.Length);
        }

        private void GetRequestRootElement()
        {
            // TODO: Review this area as we should be using strong typed objects
            // for the request and response instead of strings.
            // TODO: Should we throw an exception here instead of being lenient
            if (!String.IsNullOrEmpty(RequestMessage))
            {
                XmlDocument message = new XmlDocument();
                message.LoadXml(this.RequestMessage);
                XmlDocument xBody = new XmlDocument();
                XmlNodeList xn = message.SelectNodes("//*[local-name()='Body']");
                // the request message will be in the inner xml of the body.
                xBody.LoadXml(xn[0].InnerXml);
                if (xBody != null)
                {
                    this.RequestRootElementName = xBody.DocumentElement.Name.ToString();
                }
            }
        }

        #endregion


        #region "implementation methods"
        public void WriteResponse(XmlDocument responseDoc)
        {
            //Load the response message into the context
            this.ResponseMessage = responseDoc;
        }
        #endregion
    }
}
